/*
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *
 *   版权所有  2014-2015 成都星锐蓝海网络科技有限公司
 *   商业许可请联系  +86-18682011860    QQ:66442834
 *   
 */


#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <bits/sockaddr.h>
#include <linux/netlink.h>

#include "xyz_log.h"
#include "wtpnl.h"
#include "util.h"

static int S_nlfd;
static struct sockaddr_nl S_nlsaddr;
static struct sockaddr_nl S_nldaddr;

int wtpnlNewConnect(void)
{
	int fd = -1;

	if ((fd = socket(AF_NETLINK, SOCK_DGRAM, NL_SELF_PROTO)) < 0){
    	xyz_log_error("socket : %s", strerror(errno));
		return -1;
    }

	S_nlsaddr.nl_family = AF_NETLINK;
	S_nlsaddr.nl_pad = 0;
	S_nlsaddr.nl_pid = getpid();
	S_nlsaddr.nl_groups = 0;

#if 1
	S_nldaddr.nl_family = AF_NETLINK;
	S_nldaddr.nl_pad = 0;
	S_nldaddr.nl_pid = 0;
	S_nldaddr.nl_groups = 0;
#endif
	if (utilSetNonblock(fd)) {
		xyz_log_error("Set NETLINK socket no block error");
		close(fd);
		return -1;
	}
    if (bind(fd, (struct sockaddr *)&S_nlsaddr, sizeof(struct sockaddr_nl)) < 0){
    	xyz_log_error("bind : %s", strerror(errno));
		close(fd);
		return -1;
    }
    S_nlfd = fd;
    xyz_log_debug("NETLINK FD=%d", fd);
    return fd;
}

int wtpnlRcvdPrep(NLBUFF *nlmsg)
{
	struct nlmsghdr *nlhd = NULL;
	if (!nlmsg) {
		xyz_log_error("Arg error");
		return -1;
	}
	nlhd = (struct nlmsghdr *)(nlmsg->buff);
	nlmsg->offset = NLMSG_HDRLEN;
	nlmsg->len = nlhd->nlmsg_len - NLMSG_HDRLEN;
	if (nlmsg->len < 0) {
		xyz_log_error("NETLINK message payload length %d is too short", nlmsg->len);
		return -1;
	}
	nlmsg->type = nlhd->nlmsg_type;
	return 0;
}

/*
 *
 */
int wtpnlTimeRecv(const int fd, int to, NLBUFF *buff)
{
	int rc = 0;
	int len = 0;
	fd_set fset;
	struct timeval tv;
	int maxfd = 0;

	if (fd < 0 || to < 0 || !buff) {
		xyz_log_error("Args error");
		return -1;
	}
	tv.tv_sec = to;
	tv.tv_usec = 0;
	maxfd = fd;

	do {
		FD_ZERO(&fset);
		FD_SET(fd, &fset);
		rc = select(maxfd + 1, &fset, NULL, NULL, &tv);
		if (rc > 0) {
			if (FD_ISSET(fd, &fset)) {
				len = recv(fd, buff->buff, NL_SELF_SIZE, 0);
				if (len < 1) {
					xyz_log_error("recv : %s", strerror(errno));
					return -1;
				}
				buff->len = len;
				if (wtpnlRcvdPrep(buff)) {
					xyz_log_error("Prep NETLINK message error");
					return -1;
				}
				return 0;
			}
		}
	} while (rc > 0);

	return -1;
}

int wtpnlRecv(const int fd)
{
	int rc = 1;
	int len  = 0;
	struct nlmsghdr *nlhd = NULL;
	NLBUFF nlmsg = NLBUFF_INIT;

	len = recv(fd, nlmsg.buff, NL_SELF_SIZE, 0);
	if (len < 1 ) {
		xyz_log_error("recv : %s", strerror(errno));
		return -1;
	}
	if (len < NLMSG_HDRLEN) {
		xyz_log_error("Message is too short %d bytes", len);
		return -1;
	}
	nlhd = (struct nlmsghdr *)(nlmsg.buff);
	nlmsg.offset = NLMSG_HDRLEN;
	nlmsg.len = len;
	nlmsg.type = nlhd->nlmsg_type;

	xyz_log_debug("AC message type=%d, length=%d", nlmsg.type, len);
	switch(nlmsg.type){
	case NLMSG_TYPE_NEWUSR_REQ :
		if (wtpnlmsgHandleNewUserReq(&nlmsg)) {
			xyz_log_error("Handle NEW-USER-REQ error");
			rc = -1;
		}
		break;
	case NLMSG_TYPE_USRACCR_REP :
		if (wtpnlmsgHandleUserAccrRep(&nlmsg)) {
			xyz_log_error("Handle USER-ACCR-REQ error");
			rc = -1;
		}
		break;
	case NLMSG_TYPE_USRLEFT_REQ :
		if (wtpnlmsgHandleUserLeftReq(&nlmsg)) {
			xyz_log_error("Handle USER-LEFT-REQ error");
			rc = -1;
		}
		break;
	case NLMSG_TYPE_USRDROP_REP :
		if (wtpnlmsgHandleUserDropRep(&nlmsg)) {
			xyz_log_error("Handle USER-DROP-REQ error");
			rc = -1;
		}
		break;
	case NLMSG_TYPE_USRACCT_REQ :
		if (wtpnlmsgHandleUserAcctReq(&nlmsg)) {
			xyz_log_error("Handle USER-ACCT-REQ error");
			rc = -1;
		}
		break;
	default :
		xyz_log_error("Unknown NETLINK message type=%d, length=%d", nlmsg.type, nlmsg.len);
		break;
	}
	return rc;
}

int wtpnlSend(uint16_t type, void *msg, int len)
{
	int rc = 0;
	char buff[NL_SELF_SIZE] = { 0 };
	struct nlmsghdr *nlhd = NULL;

	if (!msg) {
		xyz_log_error("Argument error");
		return -1;
	}
	if (len > (NL_SELF_SIZE - sizeof(struct nlmsghdr))) {
		xyz_log_error("Message is too long %d bytes", len);
		return -1;
	}
	nlhd = (struct nlmsghdr *)buff;
	nlhd->nlmsg_type = type;
	nlhd->nlmsg_flags = 0;
	nlhd->nlmsg_pid = S_nlsaddr.nl_pid;
	nlhd->nlmsg_len = len + sizeof(struct nlmsghdr);
	memcpy(buff+sizeof(struct nlmsghdr), msg, len);

	rc = send(S_nlfd, buff, nlhd->nlmsg_len, 0);
	xyz_log_debug("Send NETLINK %d bytes", rc);
	if (rc < 0) {
		xyz_log_error("send : %s", strerror(errno));
		rc = -1;
	}
	return 0;
}

void wtpnlClose(void)
{
	if (S_nlfd > 0) {
		close(S_nlfd);
	}
}


/////////////////////////////////////////////////////////////////////////////////

int nlbuffRawPut(NLBUFF *buff, void *value, int len)
{
	if (!buff || !value || len < 1) {
		xyz_log_error("Arguments error");
		return -1;
	}
	if (buff->offset + len > NL_SELF_SIZE) {
		xyz_log_error("Maximum message length exceeded");
		return -1;
	}
	if (!memcpy(&(buff->buff[buff->offset]), value, len)) {
		xyz_log_error("memcpy : %s", strerror(errno));
		return -1;
	}
	buff->offset += len;
	return 0;
}

/*
 *
 */
int nlbuffPutBuff(NLBUFF *nb, BUFF *b)
{
    /*
	if (!nb || !b) {
		xyz_log_error("Arguments error");
		return -1;
	}
	if (nlbuffPutU16(nb, b->type)) {
		xyz_log_error("NETLINK buffer put error");
		return -1;
	}
	if (nlbuffPutU16(nb, b->offset)) {
		xyz_log_error("NETLINK buffer put error");
		return -1;
	}
	if (b->offset > 0) {
		if (nlbuffPutBytes(nb, b->buff, b->offset)) {
			xyz_log_error("NETLINK buffer put error");
			return -1;
		}
	}
        */
	return 0;
}

/*
 *
 */
int nlbuffRawGet(NLBUFF *buff, void *value, int len)
{
	if (!buff || !value || len < 1) {
		xyz_log_error("Arguments error");
		return -1;
	}
	if (buff->offset + len > buff->len) {
		xyz_log_error("Maximum message length exceeded");
		return -1;
	}
	if (!memcpy(value, &(buff->buff[buff->offset]), len)) {
		xyz_log_error("memcpy : %s", strerror(errno));
		return -1;
	}
	buff->offset += len;
	return 0;
}

/*
 *
 */
int nlbuffGetTLV(NLBUFF *buff, BUFF *t)
{
	uint16_t type = 0;
	uint16_t length = 0;

	if (!buff || !t) {
		xyz_log_error("Args error");
		return -1;
	}
	if (nlbuffGetU16(buff, &type)) {
		return -1;
	}
	if (nlbuffGetU16(buff, &length)) {
		return -1;
	}
	if (buff->offset + length > buff->len) {
		xyz_log_error("Require %d bytes, but there is %d bytes\n", length, buff->len);
		return -1;
	}
	t->type = type;
	t->len = length;
	nlbuffGetBytes(buff, t->buff, length);
	t->offset = 0;
	return 0;
}

//////////////////////////////////////////////////////////////////////////////////
#if 0

int main(int argc, char **argv)
{
	int nlfd = -1;
	NLBUFF nlmsg = NLBUFF_INIT;
	BUFF attr;

	logOpen("stdout", 7);
	nlfd = wtpnlNewConnect();
	if (nlfd < 1) {
		xyz_log_error("New NETLINK socket error");
		return -1;
	}
	nlmsg.type = NLMSG_TYPE_HOOK_OPEN;

	buffInit(&attr, NLMSG_ATTR_HOOK_OPEN);
	buffPutU32(&attr, 1);
	buffPutStr(&attr, "wlan0", sizeof("wlan0") -1);

	nlbuffPutBuff(&nlmsg, &attr);

	wtpnlSendNLBuff(&nlmsg);
	return 0;
}

#endif
